package com.gzist.zzchatroomserver.websocket;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gzist.zzchatroomserver.entity.po.Message;
import com.gzist.zzchatroomserver.service.GroupMemberService;
import com.gzist.zzchatroomserver.service.MessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

// 此处websocket服务地址例如：ws://127.0.0.1:8010/chat/1

/**
 * WebSocket服务端
 */
@ServerEndpoint(value = "/chat/{userId}")
@Component
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    // 心跳相关常量
    private static final String HEARTBEAT_MESSAGE = "PING"; // 服务端发送的心跳消息
    private static final long HEARTBEAT_INTERVAL = 30; // 心跳间隔时间（秒）
    private static final long TIMEOUT = 60; // 客户端超时时间（秒）
    // 线程池，用于定时发送心跳消息
    private static ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(20);
    // 当前在线连接数
    private static int onlineCount = 0;
    // 存放每个客户端对应的WebSocketServer对象
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    // 当前客户端的连接会话
    private Session session;
    /**
     * 当前客户端的用户ID
     */
    private String userId = "";

    // 上次接收到心跳消息的时间
    private long lastHeartbeatTime = System.currentTimeMillis();
    // 当前客户端的心跳任务引用
    private ScheduledFuture<?> heartbeatTask;
    /**
     * 消息服务
     * 当@Autowired注解和全参构造器都没办法注入对象值的时候可以使用下面这种方式
     */
    private static MessageService messageService;

    /**
     * 群组成员服务
     */
    private static GroupMemberService groupMemberService;

    @Autowired
    public void setMessageService(MessageService messageService) {
        WebSocketServer.messageService = messageService;
    }

    @Autowired
    public void setGroupMemberService(GroupMemberService groupMemberService) {
        WebSocketServer.groupMemberService = groupMemberService;
    }

    /**
     * 连接建立成功时调用的方法。
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        // 如果用户已存在，先移除旧连接，再添加新连接
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
        }
        webSocketMap.put(userId, this);
        // 在线人数加1
        addOnlineCount();
        log.info("用户[{}]连接成功，当前在线人数为: {}", userId, getOnlineCount());

        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("向用户[{}]发送连接成功消息时发生异常: {}", userId, e.getMessage());
        }
        // 启动心跳检测任务
        startHeartbeat();
        // todo 查询连接的用户是否存在待接收的消息，如果存在则发送

    }

    /**
     * 连接关闭时调用的方法。
     */
    @OnClose
    public void onClose() {
        // 从用户映射中移除该用户
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            subOnlineCount();
        }
        // 取消心跳任务
        if (heartbeatTask != null) {
            heartbeatTask.cancel(false);
        }
        log.info("用户[{}]退出，当前在线人数为: {}", userId, getOnlineCount());
    }

    /**
     * 收到客户端消息时调用的方法。
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到用户[{}]的消息: {}", userId, message);

        if (StrUtil.isNotBlank(message)) {
            try {
                // 如果收到心跳消息，更新最后心跳时间
                if (HEARTBEAT_MESSAGE.equals(message)) {
                    this.lastHeartbeatTime = System.currentTimeMillis();
                    log.info("收到用户[{}]的心跳消息，更新最后心跳时间", userId);
                } else {
                    // 处理客户端发送的普通消息
                    processMessage(message);
                }
            } catch (Exception e) {
                log.error("处理用户[{}]的消息时发生异常: {}", userId, e.getMessage());
            }
        }
    }

    /**
     * 处理客户端发送的消息。
     */
    private void processMessage(String message) {
        log.info("处理用户[{}]的消息内容: {}", userId, message);
        Message messageBody = new Message();
        JSONObject jsonObject;
        try {
            jsonObject = JSONUtil.parseObj(message);
            messageBody.setSenderId(jsonObject.getStr("senderId"));
            messageBody.setReceiverId(jsonObject.getStr("receiverId"));
            messageBody.setContent(jsonObject.getStr("content"));
            messageBody.setReceiverType(jsonObject.getInt("receiverType"));
            messageBody.setTimestamp(jsonObject.getLocalDateTime("timestamp", LocalDateTime.now()));
        } catch (Exception e) {
            log.error("解析用户[{}]的消息时发生异常: {}", userId, e.getMessage());
            return;
        }
        // 将消息保存到数据库
        messageService.save(messageBody);
        // 转发消息
        messageForwarding(message, messageBody);
    }

    /**
     * 消息转发逻辑。
     */
    private void messageForwarding(String message, Message messageBody) {
        if (messageBody.getReceiverType().equals(1)) {
            // 单聊消息
            try {
                sendOneMessage(message, messageBody.getSenderId());
                sendOneMessage(message, messageBody.getReceiverId());
                log.info("用户[{}]发送单体消息到[{}]，报文: {}", messageBody.getSenderId(), messageBody.getReceiverId(), message);
            } catch (IOException e) {
                log.error("发送单体消息时发生异常: {}", e.getMessage());
            }
        } else if (messageBody.getReceiverType().equals(2)) {
            // 群聊消息
            try {
                sendOneMessage(message, messageBody.getSenderId());
                List<String> groupMemberIds = groupMemberService.getGroupMemberIds(messageBody.getReceiverId());
                sendMoreMessage(message, groupMemberIds);
            } catch (IOException e) {
                log.error("发送群组消息时发生异常: {}", e.getMessage());
            }
        } else {
            log.error("用户[{}]发送消息时发生异常: {}", userId, "接收者类型错误");
        }
    }

    /**
     * 向指定用户发送消息。
     */
    public void sendOneMessage(String message, String userId) throws IOException {
        if (StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
            webSocketMap.get(userId).sendMessage(message);
        } else {
            log.warn("用户: " + userId + ",不在线！");
            // todo 用户不在线 将消息存储起来 等待其上线时再进行推送


        }
    }

    /**
     * 向多个用户发送消息。
     */
    public void sendMoreMessage(String message, List<String> userIds) throws IOException {
        for (String id : userIds) {
            if (webSocketMap.containsKey(id)) {
                webSocketMap.get(id).sendMessage(message);
            } else {
                log.warn("用户: " + id + ",不在线！");
                // todo 用户不在线 将消息存储起来 等待其上线时再进行推送


            }
        }
    }

    /**
     * 广播消息（仅在线用户能收到）。
     */
    public void sendAllMessage(String message) throws IOException {
        for (Map.Entry<String, WebSocketServer> entry : webSocketMap.entrySet()) {
            entry.getValue().sendMessage(message);
        }
    }

    /**
     * 向客户端发送消息。
     */
    public void sendMessage(String message) throws IOException {
        this.session.getAsyncRemote().sendText(message);
    }

    /**
     * 启动心跳检测任务。
     */
    private void startHeartbeat() {
        // 如果已有心跳任务，取消旧任务
        if (heartbeatTask != null) {
            heartbeatTask.cancel(false);
        }
        // 创建新的心跳任务
        heartbeatTask = heartbeatExecutor.scheduleAtFixedRate(() -> {
            long currentTime = System.currentTimeMillis();
            if (currentTime - this.lastHeartbeatTime > TIMEOUT * 1000) {
                // 超过超时时间未收到心跳，关闭连接
                log.warn("用户[{}]超时未响应心跳，关闭连接", userId);
                try {
                    session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "超时未响应心跳"));
                } catch (IOException e) {
                    log.error("关闭用户[{}]的连接时发生异常: {}", userId, e.getMessage());
                }
                webSocketMap.remove(userId);
                subOnlineCount();
            } else {
                // 发送心跳消息
                try {
                    sendMessage(HEARTBEAT_MESSAGE);
                } catch (IOException e) {
                    log.error("向用户[{}]发送心跳消息时发生异常: {}", userId, e.getMessage());
                }
            }
        }, 0, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
    }

    /**
     * 获取当前在线人数。
     */
    public synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 在线人数加1。
     */
    public synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    /**
     * 在线人数减1。
     */
    public synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    /** ！！！
     * 对于客户端方面，一般只需要做对服务端心跳的响应即可，如果需要考虑其他因素导致服务端
     * 心跳消息丢失，那么在客户端 1，可以实现与服务端类似的定时发送心跳包的策略 2.可以实现
     * 在自身断链后重新尝试连接的策略 这样便可以保持连接正常
     */
}
